#ifndef __FS_WIN_H__
#define __FS_WIN_H__

#include "crossplatform.h"
#include "dokan.h"
#include "configuration.h"
#include "errors.h"
#include "provider_api.h"

class FilesystemProvider;
class ProviderFile;
class ProviderCommon;

class MountedFilesystem
{
public:
	virtual ~MountedFilesystem() {}
	virtual void Unmount() = 0;
};

class DokanFilesystemMount : public MountedFilesystem
{
public:
	DokanFilesystemMount(FilesystemProvider *provider, const std::wstring& mount, const Configuration& cfg);
	~DokanFilesystemMount();
	virtual void Unmount();

	HANDLE GetWaitEvent() { return MountWait; }

	int OpenRootDir(PDOKAN_FILE_INFO info);

	int CreateFile(const std::string& FileName, DWORD DesiredAccess, DWORD ShareMode, DWORD CreationDisposition, DWORD FlagsAndAttributes, PDOKAN_FILE_INFO info);
	int OpenDirectory(const std::string& FileName, PDOKAN_FILE_INFO info);
	int CreateDirectory(const std::string& FileName, PDOKAN_FILE_INFO info);
	int Cleanup(PDOKAN_FILE_INFO info);
	int Close(PDOKAN_FILE_INFO info);
	int Move(const std::string& from, const std::string& to, int replace);

	int GetDiskFreeSpace(PULONGLONG  FreeBytesAvailable, PULONGLONG  TotalNumberOfBytes, PULONGLONG  TotalNumberOfFreeBytes);
	int GetVolumeInformation(std::wstring& friendlyname, std::wstring& fstype, DWORD& serial, DWORD& componentlength, DWORD& flags);

	int GetFileInformation(LPBY_HANDLE_FILE_INFORMATION Buffer, PDOKAN_FILE_INFO info);

//	int FindFiles(const std::string& path, PFillFindData fn, PDOKAN_FILE_INFO info);

private:
	FilesystemProvider *Provider;
	std::wstring Mount;
	std::wstring FriendlyName;

	ProviderDir * volatile RootDir;
	CriticalSection RootDirOpenLock;

	//as it turns out, there's not much of a way to determine if
	//we actually got mounted by Dokan since it uses a blocking
	//main-type function, so we wait for a call to come in
	bool IsMounted;
	HANDLE MountWait;

	DWORD SerialNumber;
};

void FsInit();
MountedFilesystem* FsMount(const char *mount, FilesystemProvider *provider, const Configuration& cfg);

inline ProviderFile* GetFileObj(PDOKAN_FILE_INFO info)
{
	if(info->Context == NULL)
		return NULL;
	ProviderFile *f = ((ProviderCommon*)info->Context)->File();
	if(f)
		info->IsDirectory = FALSE;
	return f;
}
inline ProviderDir* GetDirObj(PDOKAN_FILE_INFO info)
{
	if(info->Context == NULL)
		return NULL;
	ProviderDir *d = ((ProviderCommon*)info->Context)->Dir();
	if(d)
		info->IsDirectory = TRUE;
	return d;
}

inline ProviderCommon* GetCommonObj(PDOKAN_FILE_INFO info)
{
	return (ProviderCommon*)info->Context;
}

inline DWORD GetAttributesFor(const DirStatInfo& info)
{
	DWORD ret;
	switch(info.Attributes & STAT_FLAG_TYPE_MASK) {
	case STAT_FLAG_IS_DIRECTORY:
		ret = FILE_ATTRIBUTE_DIRECTORY;
		break;
	case STAT_FLAG_IS_BLOCKDEV: //fallthrough
	case STAT_FLAG_IS_CHARDEV: //fallthrough
	case STAT_FLAG_IS_SOCKET: //fallthrough
	case STAT_FLAG_IS_FIFO:
		ret = FILE_ATTRIBUTE_DEVICE | FILE_ATTRIBUTE_SYSTEM;
		break;
	case STAT_FLAG_IS_FILE:
	default:
		ret = FILE_ATTRIBUTE_NORMAL;
		break;
	}
	if((info.Attributes & STAT_FLAG_IS_SYMLINK) != 0) {
		ret |= FILE_ATTRIBUTE_REPARSE_POINT;
	}
	if((info.Attributes & STAT_FLAG_READ_ONLY) != 0)
		ret |= FILE_ATTRIBUTE_READONLY;
	if((info.Attributes & STAT_FLAG_HIDDEN) != 0)
		ret |= FILE_ATTRIBUTE_HIDDEN;
	return ret;
}

#endif
